/*
 * REFAutoReleasePool.cpp
 *
 *  Created on: Nov 26, 2011
 *      Author: Lucifer
 */

#include "../pch.h"
#include "../common/HashTraits.h"

namespace BFX
{

//////////////////////////////////////////////////////////////////////////////
// 0x1f
#define	REFPOOL_HASH_MASK 0x3f

typedef	HashTraits<const REFObject*>	REFObjectHashTraits;

// Static objects
SpinLock	REFAutoreleasePool::s_initLock;
bool		REFAutoreleasePool::s_bInitialized = false;
HANDLE		REFAutoreleasePool::s_hTlsSlot = NULL;

// Constructions
REFAutoreleasePool::REFAutoreleasePool()
	: m_pPoolPrev(NULL), m_nSize(0), m_pBuckets(new REF<Entry>[REFPOOL_HASH_MASK + 1])
{
	StaticInitialize();

	// push self into the pool stack.
#ifdef	_WIN32
	m_pPoolPrev = (REFAutoreleasePool*)::TlsGetValue((DWORD)s_hTlsSlot);
	::TlsSetValue((DWORD)s_hTlsSlot, this);
#else
	m_pPoolPrev = (REFAutoreleasePool*)pthread_getspecific((pthread_key_t)s_hTlsSlot);
	pthread_setspecific((pthread_key_t)s_hTlsSlot, this);
#endif
}

REFAutoreleasePool::~REFAutoreleasePool()
{
	Clear();
	delete[] m_pBuckets;

#ifdef	_WIN32
	::TlsSetValue((DWORD)s_hTlsSlot, m_pPoolPrev);
#else
	pthread_setspecific((pthread_key_t)s_hTlsSlot, m_pPoolPrev);
#endif
	m_pPoolPrev = NULL;
}

// Initializes static resources.
// But actually, that is no necessary to call this method manually.
void REFAutoreleasePool::StaticInitialize()
{
	if (!s_bInitialized)
	{
		SpinLockHolder holder(&s_initLock);

		if (!s_bInitialized)
		{
#ifdef	_WIN32
			BFX_ASSERT(s_hTlsSlot == 0);
			s_hTlsSlot = (HANDLE)::TlsAlloc();
#else
			int nRetCode = pthread_key_create((pthread_key_t*)&s_hTlsSlot, NULL);
			BFX_ENSURE(nRetCode == 0);
#endif
			s_bInitialized = true;
		}
	}
}

// Cleanup static resources.
// But actually, that is no necessary to call this method manually.
void REFAutoreleasePool::StaticFinalize()
{
	if (s_bInitialized)
	{
		SpinLockHolder holder(&s_initLock);

		if (s_bInitialized)
		{
			// release TLS key.
#ifdef	_WIN32
			::TlsFree((DWORD)s_hTlsSlot);
#else
			pthread_key_delete((pthread_key_t)s_hTlsSlot);
#endif

			s_bInitialized = false;
		}
	}
}

// Get current autorelease pool, or create new pool if not exists (with 0 refcount).
REFAutoreleasePool* REFAutoreleasePool::CreateOrGetCurrentPool()
{
	// attempt to get current autorelease pool.
	REF<REFAutoreleasePool> pCurPool = (REFAutoreleasePool*)
#ifdef	_WIN32
		::TlsGetValue((DWORD)s_hTlsSlot);
#else
		pthread_getspecific((pthread_key_t)s_hTlsSlot);
#endif
	// create new instance if not exists.
	if (pCurPool == NULL)
	{
		pCurPool = new REFAutoreleasePool();
		// NOTE: Don't add ref here.
	}
	return pCurPool;
}

// Adds a given object to the active auto-release pool in the current thread.
void REFAutoreleasePool::AddObject(const REFObject *pObject)
{
	BFX_REQUIRE(pObject != NULL);

	REFAutoreleasePool* pCurPool = (REFAutoreleasePool*)
#ifdef	_WIN32
		::TlsGetValue((DWORD)s_hTlsSlot);
#else
		pthread_getspecific((pthread_key_t)s_hTlsSlot);
#endif

	BFX_ENSURE(pCurPool != NULL);
	pCurPool->Add(pObject);
}

// Releases and pops all objects in the active auto-release pool.
void REFAutoreleasePool::Drain()
{
	REFAutoreleasePool* pCurPool = (REFAutoreleasePool*)
#ifdef	_WIN32
		::TlsGetValue((DWORD)s_hTlsSlot);
#else
		pthread_getspecific((pthread_key_t)s_hTlsSlot);
#endif

	BFX_ENSURE(pCurPool != NULL);
	pCurPool->Clear();
}

void REFAutoreleasePool::Add(const REFObject *pObject)
{
	BFX_REQUIRE(pObject != NULL);

	// resolve the bucket index of given object.
	int nHashCode = REFObjectHashTraits::GetHashCode(pObject);
	int nHashBucket = nHashCode & REFPOOL_HASH_MASK;
	Entry* pEntry;

	// lookup entry for duplicates
	for (pEntry = m_pBuckets[nHashBucket]; pEntry != NULL; pEntry = pEntry->m_pNext)
	{
		if (pEntry->m_nHashCode == nHashCode && REFObjectHashTraits::Equals(pEntry->m_pObject, pObject))
			return;	// exists, skip.
	}

	// create a new entry
	pEntry = new Entry();
	pEntry->m_pObject = pObject;
	pEntry->m_nHashCode = nHashCode;

	// put into hash table
	pEntry->m_pNext = m_pBuckets[nHashBucket];
	m_pBuckets[nHashBucket] = pEntry;
	m_nSize ++;
}

void REFAutoreleasePool::Remove(const REFObject *pObject)
{
	BFX_REQUIRE(pObject != NULL);

	// resolve the bucket index of given object.
	int nHashCode = REFObjectHashTraits::GetHashCode(pObject);
	int nHashBucket = nHashCode & REFPOOL_HASH_MASK;

	// lookup entry
	Entry* pEntryPrev = NULL;
	for (Entry* pEntry = m_pBuckets[nHashBucket]; pEntry != NULL; pEntry = pEntry->m_pNext)
	{
		if (pEntry->m_nHashCode == nHashCode && REFObjectHashTraits::Equals(pEntry->m_pObject, pObject))
		{
			// remove it
			if (pEntryPrev != NULL)
				pEntryPrev->m_pNext = pEntry->m_pNext;
			else
				m_pBuckets[nHashBucket] = pEntry->m_pNext;
			m_nSize --;
			return;
		}
		pEntryPrev = pEntry->m_pNext;
	}
}

void REFAutoreleasePool::Clear()
{
	if (m_nSize == 0)
		return;

	for (int nHashBucket = 0; nHashBucket < (REFPOOL_HASH_MASK + 1); nHashBucket++)
	{
		m_pBuckets[nHashBucket] = NULL;	// don't worry, that's fine.
	}
	m_nSize = 0;
}

} /* namespace BFX */
